﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using System.Xml.Linq;
using Ninject;
using OAuth;

namespace InstantWatch {
	/// <summary>
	/// Represents the connection to the Netflix servers.
	/// Netflix API:  http://developer.netflix.com/docs/Security#0_18325
	/// Useful Source:  http://www.codeproject.com/KB/IP/NetflixAPIBasics.aspx#UnravelingSignedRequests7
	/// </summary>
	public class NetflixProxy : AProxy {

		#region CONST
		public const string NETFLIX_API = @" http://api.netflix.com";
		public const string GET_INSTANT_QUEUE = @"/users/{0}/queues/instant";

		public const string NETFLIX_WATCH_LINK_BASE = @"http://www.netflix.com/WiPlayer?movieid=";
		public const string SYNOPSIS_LINK_PREFIX = @"http://api.netflix.com/catalog/titles/movies/";
		/// <summary>
		/// URL to get the current time from the Netflix server
		/// {0} = this.ConsumerKey
		/// </summary>
		/// <remarks>
		/// the cryptographic method you're going to use to sign your call. Always send HMAC-SHA1, in upper-case letters.
		/// </remarks>
		public const string GET_TIMESTAMP = @"http://api.netflix.com/oauth/clock/time?oauth_consumer_key={0}";
		/// <summary>
		/// optional value for OAuth version. If included, this must be set to 1.0. We will be including it throughout this page and think you should too, to help you keep in mind that the OAuth spec is a living document, and will undoubtedly be updated in the future
		/// </summary>
		public const string OAUTH_VERSION = "1.0";

		public const string SIG_METHOD = "HMAC-SHA1";
		#endregion

		[Inject]
		public AuthTokens AuthTokens { get; set; }

		private int secondsDelta = int.MinValue;

		/// <summary>
		/// a randomly-generated string of characters that helps to prevent replay attacks. Please be sure to make these characters legal to percent-encode or include in URLs, and to regenerate a fresh random string for every OAuth call. Throughout this page we will use abcdefghijk, which would work the first time you tried it--assuming everything else was legit--but not a second time for the same call.
		/// </summary>
		public string Nonce {
			get {
				int len = 5;
				string letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";

				StringBuilder sb = new StringBuilder(len);
				Random rand = new Random(DateTime.Now.Millisecond);
				for (int i = 0; i < len; i++) {
					sb.Append(letters[rand.Next(letters.Length)]);
				}
				return sb.ToString();
			}
		}


		/// <summary>
		/// the number of seconds elapsed since midnight, 1 January 1970. Please be sure this is within ten minutes of real time, and also please understand that client-side apps will be at the mercy of their system clocks, and will fail with bad timestamp errors if they're too far off. Throughout this page we will use 1234567890, which will not work if you try it because it came and went on February 13th, 2009. You can get a timestamp from our system by calling http://api.netflix.com/oauth/clock/time?oauth_consumer_key=KEY to ensure that your timestamp will match.  For your users, you might see if the client has the right time and work from there
		/// </summary>
		public int SecondsSince1970 {
			get {
				if (secondsDelta == int.MinValue) {
					// As long as this line returns within 10 minutes, our calculations will be close enough
					int serverSeconds = GetServerSecondsSince1970();
					int localSeconds = (int)(DateTime.UtcNow - new DateTime(1970, 1, 1)).TotalSeconds;

					secondsDelta = serverSeconds - localSeconds;
				}
				return (int)(DateTime.Now.AddSeconds(secondsDelta) - new DateTime(1970, 1, 1)).TotalSeconds;
			}
		}

		private int GetServerSecondsSince1970() {
			WebClient wc = new WebClient();

			string reply = wc.DownloadString(string.Format(GET_TIMESTAMP, this.AuthTokens.ConsumerKey));
			int begin = reply.IndexOf('>');
			int end = reply.LastIndexOf('<');
			int len = end - begin - 1;

			int secondsSince1970 = -1;
			int.TryParse(reply.Substring(begin + 1, len), out secondsSince1970);

			return secondsSince1970;
		}

		public override IEnumerable<QueueItem> GetUserQueue(int count) {
			string xml = GetUserQueueString(20);
			XElement queue = XElement.Parse(xml);

			var items = from q in queue.Descendants("queue_item")
									select
									FromNetflix(q);
			return items;
		}

		public string GetUserQueueString(int count) {
			using (WebClient wc = new WebClient()) {
				OAuthBase oauth = new OAuthBase();

				// inputs
				oauth.AddQueryParameter("max_results", count.ToString());
				string baseUrl = NETFLIX_API + string.Format(GET_INSTANT_QUEUE, this.AuthTokens.UserId);

				string reqUrl = CreateOAuthRequest(oauth, AuthTokens.UserKey, AuthTokens.UserSecret, baseUrl);
				return wc.DownloadString(reqUrl);
			}

		}

		public string SimpleGetExample2() {
			using (WebClient wc = new WebClient()) {
				OAuthBase oauth = new OAuthBase();

				// inputs
				oauth.AddQueryParameter("term", "Space");
				oauth.AddQueryParameter("max_results", "10");

				string reqUrl = CreateOAuthRequest(oauth, "http://api.netflix.com/catalog/titles/");
				return wc.DownloadString(reqUrl);
			}
		}

		#region Helpers
		private string CreateOAuthRequest(OAuthBase oauth, string requestString) {
			return CreateOAuthRequest(oauth, null, null, requestString);
		}

		private string CreateOAuthRequest(OAuthBase oauth, string token, string tokenSecret, string requestString) {
			Uri requestUrl = new Uri(requestString);

			// outputs
			string normalizedUrl;
			string normalizedRequestParameters;

			// generate request signature
			string sig = oauth.GenerateSignature(requestUrl,
											 this.AuthTokens.ConsumerKey, this.AuthTokens.ConsumerSecret,
											 token, tokenSecret,    // token , tokenSecret (not needed)
											 "GET", oauth.GenerateTimeStamp(), oauth.GenerateNonce(),
											 out normalizedUrl, out normalizedRequestParameters);
			// construct request
			string reqUrl = requestUrl + "?" +
											normalizedRequestParameters +
											"&oauth_signature=" +
											oauth.UrlEncode(sig);
			return reqUrl;
		}

		public static Dictionary<string, string> ParseResponse(string response) {
			string[] elements = response.Split('&');
			Dictionary<string, string> lookup = new Dictionary<string, string>();
			foreach (string elem in elements) {
				string[] item = elem.Split('=');
				lookup.Add(item[0], item[1]);
			}

			return lookup;
		}
		#endregion

		#region UserAuth
		public override void RequestAuthorization() {
			Dictionary<string, string> tokens = Auth1GetRequestUrl();
			bool authGranted = Auth2DisplayAuthScreenToUser(tokens);

			Console.WriteLine(tokens["oauth_token"] + " - " + tokens["oauth_token_secret"]);

			if (authGranted) {
				Dictionary<string, string> userTokens = Auth3GetUserTokens(tokens);


				this.AuthTokens.UserKey = userTokens["oauth_token"];
				this.AuthTokens.UserSecret = userTokens["oauth_token_secret"];
				this.AuthTokens.UserId = userTokens["user_id"];

				this.AuthTokens.Save();
			}

			//return this.AuthTokens.UserId.ToString();
		}

		private Dictionary<string, string> Auth3GetUserTokens(Dictionary<string, string> tokens) {
			using (WebClient wc2 = new WebClient()) {

				OAuthBase oauth2 = new OAuthBase();
				string reqUrl = CreateOAuthRequest(oauth2, tokens["oauth_token"], tokens["oauth_token_secret"], "http://api.netflix.com/oauth/access_token");
				return ParseDownloadedString(wc2, reqUrl);
			}
		}

		private bool Auth2DisplayAuthScreenToUser(Dictionary<string, string> tokens) {
			// Step 2:  Get user to "OK" your app 
			string userUrl = string.Format("{0}&oauth_consumer_key={1}&application_name={2}",
				HttpUtility.UrlDecode(tokens["login_url"]),
				this.AuthTokens.ConsumerKey,
				tokens["application_name"]);

			bool authGranted = new AuthBrowser(new Uri(userUrl)).ShowDialog().Value;
			return authGranted;
		}

		private Dictionary<string, string> Auth1GetRequestUrl() {
			Dictionary<string, string> tokens;
			// Step 1:  Get URL to show to user
			using (WebClient wc = new WebClient()) {
				OAuthBase oauth = new OAuthBase();

				string reqUrl = CreateOAuthRequest(oauth, "http://api.netflix.com/oauth/request_token");
				tokens = ParseDownloadedString(wc, reqUrl);
			}
			return tokens;
		}
		#endregion

		public static QueueItem FromNetflix(XElement xml) {
			QueueItem item = new QueueItem();

			item.Name = xml.Descendants("title").Attributes("regular").First<XAttribute>().Value;

			string idBase = xml.Descendants("id").First().Value;
			int idx = idBase.LastIndexOf(@"/");
			item.Id = idBase.Substring(idx + 1);


			//Box art URL cannot be determined off of ID alone.
			item.BoxArtSource = xml.Descendants("box_art").Attributes("large").First<XAttribute>().Value;

			item.WatchLink = NETFLIX_WATCH_LINK_BASE + item.Id;
			item.SynopsisSource = SYNOPSIS_LINK_PREFIX + item.Id + @"/synopsis";

			item.BoxArtFile = @"..\boxArt\tempBoxArt.jpg";

			string destinationPath = Path.Combine(@"boxArt\netflix\", item.Id + ".jpg");
			destinationPath = GetBoxArt(item.BoxArtSource, destinationPath);
			item.BoxArtFile = destinationPath;

			return item;
		}


		private static Dictionary<string, string> ParseDownloadedString(WebClient wc, string reqUrl) {
			string response = wc.DownloadString(reqUrl);
			Console.WriteLine(response);
			return ParseResponse(response);
		}

	}
}
